﻿// Inner Fire 游戏引擎库
// ShaderCache - 着色器缓存
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 用于保存编译之后的Shader。以免每次都重新编译。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-07-08

#include "shader_cache.h"

#include <dx/core/dx_utils.h>
#include <fstream>
#include <functional>
#include <knumber.h>
#include <klog.h>
#include <sstream>

namespace ifire::dx {

fs::path ShaderCache::s_cacheDir;

void ShaderCache::Initialize(const std::wstring& cacheDir) {
  s_cacheDir = fs::absolute(cacheDir);
  fs::create_directories(s_cacheDir);
}

Microsoft::WRL::ComPtr<ID3DBlob> ShaderCache::CompileOrLoad(
    const std::string& filename, const D3D_SHADER_MACRO* defines,
    const std::string& entry_point, const std::string& target,
    bool forceRecompile) {

  auto cachePath = GetCacheFilePath(filename, defines, entry_point, target);
  fs::path sourcePath(filename);

  // 1. 检查是否需要重新编译
  bool needRecompile = forceRecompile || !fs::exists(cachePath) ||
                       !IsCacheValid(cachePath, sourcePath);

  // 2. 如果不需要重新编译，尝试从缓存加载
  if (!needRecompile) {
    ComPtr<ID3DBlob> blob;
    if (SUCCEEDED(D3DReadFileToBlob(cachePath.c_str(), &blob))) {
      return blob;
    }
    // 如果加载失败，继续执行编译流程
  }

  // 3. 编译Shader
  auto byteCode =
      DxUtils::CompileShader(filename, defines, entry_point, target);

  // 4. 保存到缓存
  if (byteCode) {
    try {
      // 确保目录存在
      fs::create_directories(cachePath.parent_path());

      // 写入文件
      if (FAILED(D3DWriteBlobToFile(byteCode.Get(), cachePath.c_str(), TRUE))) {
        ikit::log("Failed to write shader cache: %s", cachePath.c_str());
      }
    } catch (const fs::filesystem_error& e) {
      ikit::log("Filesystem error while saving shader cache: %s", e.what());
    }
  }

  return byteCode;
}

fs::path ShaderCache::GetCacheFilePath(const std::string& filename,
    const D3D_SHADER_MACRO* defines, const std::string& entry_point,
    const std::string& target) {

  // 计算文件名哈希
  auto filenameHash = ikit::hash64(filename.c_str());
  auto definesHash = CalculateDefinesHash(defines);

  // 构建缓存文件名
  std::ostringstream oss;
  oss << std::hex << filenameHash << "_" << definesHash << "_"
      << ikit::hash(entry_point.c_str()) << "_" << ikit::hash(target.c_str()) << ".cso";

  return s_cacheDir / oss.str();
}

size_t ShaderCache::CalculateDefinesHash(const D3D_SHADER_MACRO* defines) {
  if (!defines) return 0;

  std::ostringstream oss;
  for (auto macro = defines; macro->Name != nullptr; ++macro) {
    oss << macro->Name << "=" << (macro->Definition ? macro->Definition : "")
        << ";";
  }

  return ikit::hash(oss.str().c_str());
}

bool ShaderCache::IsCacheValid(
    const fs::path& cachePath, const fs::path& sourcePath) {
  if (!fs::exists(cachePath) || !fs::exists(sourcePath)) {
    return false;
  }

  try {
    auto cacheTime = fs::last_write_time(cachePath);
    auto sourceTime = fs::last_write_time(sourcePath);

    // 如果缓存文件比源文件新，则缓存有效
    return cacheTime >= sourceTime;
  } catch (const fs::filesystem_error&) {
    // 如果无法获取时间戳，假设缓存无效
    return false;
  }
}

void ShaderCache::ClearCache() {
  if (fs::exists(s_cacheDir)) {
    fs::remove_all(s_cacheDir);
    fs::create_directories(s_cacheDir);
  }
}

} // namespace ifire::dx